/*
 * @Author: AskMeWhy
 * @Date: 2020-12-23 11:30:58
 * @Last Modified by: AskMeWhy
 * @Last Modified time: 2020-12-24 19:45:53
 */
import { DataMeta, ThMeta, TdMeta, StyleMeta, CanvasMeta } from "./excel";
import { PluginManager } from "./plugin";
import { ToolBarManager } from "./toolbar";
import { Particle } from "./particle";
import { merge, ObserverClass, ObserverEvent, throttle } from "@/utils";

export class AskExcel extends ObserverClass {
    instance: CanvasMeta = {
        canvas: null,
        ctx: null,
        scale: 0, // 当前canvas内容的缩放比例
        scrollLeft: 0, // 当前canvas内容距离最左侧的距离
        scrollTop: 0, // 当前canvas内容距离顶部的距离
        scrollWidth: 0, // 当前canvas内容的可滚动宽度
        scrollHeight: 0, // 当前canvas内容的可滚动高度
        width: 0, // 当前绘制canvas的实际宽度
        height: 0, // 当前绘制canvas的实际高度
        zIndex: 1000
    };

    $el: any = null;
    thStyle: StyleMeta = {
        width: 80,
        // height: 40,
        height: 50,
        color: "#1E1E1F",
        borderColor: "#E2E4E8",
        font: "16px caption",
        borderWidth: 1,
        bgColor: "#FFFFFF",
        textAlign: "",
        textBaseline: ""
    };

    tdStyle: StyleMeta = {
        width: 80,
        height: 50,
        color: "#fff",
        borderColor: "red",
        font: "16px caption",
        borderWidth: 1,
        bgColor: "#212121",
        textAlign: "",
        textBaseline: ""
    };

    indexStyle: StyleMeta = {
        width: 80,
        height: 50,
        color: "#1E1E1F",
        borderColor: "#E2E4E8",
        font: "16px caption",
        borderWidth: 1,
        bgColor: "#F9FAFB",
        textAlign: "center",
        textBaseline: "middle"
    };

    thData: Array<ThMeta> = [];
    tdData: Array<Array<TdMeta>> = [];
    pluginManager: PluginManager = new PluginManager();
    toolBarManager: ToolBarManager = new ToolBarManager();
    thParticles: Array<Array<Particle>> = [[], []];
    tdParticles: Array<Array<Particle>> = [];
    renderThData: Array<any> = [];
    renderTdData: Array<any> = [];
    requestAnimationID: any = null;
    tdMaxHeight: Array<number> = [];
    thMaxHeight: Array<number> = [];
    allDataHeight = 0;
    allDataWidth = 0;
    canEditorItem: any = {};
    constructor(params: DataMeta) {
        super(params);
        this.buildParam(params);
        this.init();
    }

    // 构建参数
    private buildParam(params: DataMeta) {
        let option: DataMeta = {
            el: document.body,
            thStyle: {}, // 公共的th样式，thData的style会覆盖他
            tdStyle: {}, // 公共的td样式，tdData的style会覆盖他
            thData: [],
            tdData: []
        };
        option = merge(true, option, params);

        merge(true, this, {
            $el: this.isElementNode(option.el) ? option.el : document.querySelector(option.el),
            thStyle: option.thStyle,
            tdStyle: option.tdStyle,
            thData: option.thData,
            tdData: option.tdData
        });
    }

    private init() {
        this.initCanvas();
        this.renderParticle();
        this.updateScrollInfo();
        this.draw();
        this.initEvent();
    }

    private isElementNode(node: any) {
        return node !== undefined && node.nodeType === 1;
    }

    private initCanvas() {
        const box = this.instance.canvas = document.createElement("canvas");
        if (!box.getContext) {
            this.canvasNoSupport();
            console.log("傻逼，你的浏览器不支持canvas!");
            return;
        }
        this.instance.ctx = box.getContext("2d");
        this.instance.width = box.width = this.$el.clientWidth;
        this.instance.height = box.height = this.$el.clientHeight;
        box.style.zIndex = this.instance.zIndex + "";
        box.style.position = "absolute";
        box.style.top = "0";
        box.style.left = "0";
        box.style.right = "0";
        box.style.bottom = "0";
        this.$el.appendChild(box);
    }

    private canvasNoSupport() {
        console.log('nonono');
    }

    private initEvent() {
        this.scrollEvent();
        this.clickEvent();
        this.dbClickEvent();
    }

    clickEvent() {
        const clickEvent = ObserverEvent(
            this.instance.canvas,
            "click",
            (e: any) => {
                const { offsetX, offsetY } = e;
                const { scrollLeft, scrollTop } = this.instance;
                let res = {};
                const allData = this.renderTdData.concat(this.renderThData);
                for (const cur of allData) {
                    const {
                        position: { x, y },
                        style: { width }
                    } = cur.particle;
                    const inSide = this.boundPointContain(
                        { x: offsetX + scrollLeft, y: offsetY + scrollTop },
                        {
                            x,
                            y,
                            width,
                            height: cur.height
                        }
                    );
                    if (inSide) {
                        res = cur;
                        break;
                    }
                }
                this.canEditorItem = res;
                this.emit("click", e, res);
            }
        );
        this.on("destroy", () => {
            clickEvent.destroy();
        });
    }

    dbClickEvent() {
        const dblclickEvent = ObserverEvent(
            this.instance.canvas,
            "dblclick",
            (e: any) => {
                this.emit("dblclick", e, this.canEditorItem);
            }
        );
        this.on("destroy", () => {
            dblclickEvent.destroy();
        });
    }

    scrollEvent() {
        const event = window.addWheelListener(
            this.$el,
            throttle((e: any) => {
                const { scrollLeft, scrollTop } = this.instance;
                // const k = 0.1;
                // this.scroll(scrollLeft, scrollTop + e.deltaY * k);
                this.scroll(scrollLeft, scrollTop + e.deltaY);
                e.preventDefault();
            }, 0)
        );
        this.on("destroy", () => {
            event.destroy();
        });
    }

    destroy() {
        this.instance.canvas &&
            this.instance.canvas.remove &&
            this.instance.canvas.remove();
        this.emit("destroy");
    }

    private getIndexValue(type = "num", index: number) {
        const alphaList = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"];
        const numList = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"];
        // 1,10 0 => 1
        // 10,10 0,9 => 10
        // 11,10 0,0 => 11
        // 100,10 0,9,9 => 100
        // 101,10 0,9,0 => 101
        // 111,10 0,1,1 => 111
        // 1,26 0 => A
        // 26,26 0,25 => Z
        // 27,26 0,0 => AA
        const calcStep = (value: number, step: number): number[] => {
            if (type === "num" && value < step) return [value - 1];
            if (type === "alpha" && value <= step) return [value - 1];
            let last = value % step;
            if (last === 0) {
                last = step;
            }
            last = last - 1;
            return [last].concat(calcStep(Math.floor(value / step), step));
        };
        if (type === "num") {
            const res = calcStep(index, numList.length).reverse();
            return res.map((cur: any) => numList[cur]).join("");
        }
        if (type === "alpha") {
            const res = calcStep(index, alphaList.length).reverse();
            return res.map((cur: any) => alphaList[cur]).join("");
        }
    }

    private renderParticle() {
        const { thData, tdData, instance, thStyle, tdStyle, indexStyle } = this;
        // 多少列
        // const colLength = thData.length > 26 ? thData.length : 27;
        const colLength = thData.length > 26 ? thData.length : 27;
        // 多少行
        // const rowLength = tdData.length > 200 ? tdData.length : 200;
        const rowLength = tdData.length > 200 ? tdData.length : 10000;
        let scrollWidth = 0;
        let scrollHeight = 0;
        const colData = {
            x: 0,
            y: 0
        };
        const rolData = {
            x: 0,
            y: 0
        };
        let thMaxHeight = 0;
        const tdMaxHeightArr = [];
        for (let i = 0; i < colLength; i++) {
            const cur = thData[i - 1] || {};
            const params = {
                instance: instance,
                style: indexStyle,
                position: {
                    x: colData.x,
                    y: colData.y
                },
                value: this.getIndexValue("alpha", i),
                placeholder: "",
                type: i === 0 ? "default" : "input",
                data: cur.data || {}
            };
            colData.x = colData.x + (params.style.width || 0);
            this.thParticles[0].push(new Particle(params));
            if (i !== 0) {
                params.style = thStyle;
            }
            if (cur.style) {
                params.style = {
                    ...params.style,
                    ...cur.style
                };
            }
            params.value = i === 0 ? this.getIndexValue("num", 1) : cur.value || "";
            params.position.y = colData.y + (params.style.height || 0);
            rolData.y = params.position.y + (params.style.height || 0);
            thMaxHeight = Math.max(thMaxHeight, (params.style.height || 0));
            scrollWidth += params.style.width || 0;
            this.thParticles[1].push(new Particle(params));
        }
        scrollHeight += thMaxHeight * 2;
        for (let i = 0; i < rowLength; i++) {
            const _p = [];
            let tdMaxHeight = 0;
            const params = {
                instance: instance,
                style: indexStyle,
                position: {
                    x: rolData.x,
                    y: rolData.y
                },
                value: this.getIndexValue("num", i + 2),
                placeholder: "",
                type: "index",
                data: {}
            };
            _p.push(new Particle(params));
            rolData.x = rolData.x + (params.style.width || 0);
            for (let j = 0; j < colLength - 1; j++) {
                const cur = (tdData[i] && tdData[i][j]) || {};
                params.style = tdStyle;
                if (cur.style) {
                    params.style = {
                        ...params.style,
                        ...cur.style
                    };
                }
                params.position.x = rolData.x;
                rolData.x = rolData.x + (params.style.width || 0);
                params.value = cur.value || "";
                _p.push(new Particle(params));
                tdMaxHeight = Math.max(tdMaxHeight, params.style.height || 0);
            }
            rolData.y = rolData.y + (tdMaxHeight || 0);
            rolData.x = 0;
            scrollHeight += tdMaxHeight;
            tdMaxHeightArr.push(tdMaxHeight);
            this.tdParticles.push(_p);
        }
        this.allDataWidth = scrollWidth;
        this.allDataHeight = scrollHeight;
        this.thMaxHeight = [thMaxHeight, thMaxHeight];
        this.tdMaxHeight = tdMaxHeightArr;
    }

    private draw() {
        const {
            tdParticles,
            thParticles,
            instance,
            tdMaxHeight,
            thMaxHeight
        } = this;
        instance.ctx.clearRect(0, 0, instance.width, instance.height);
        if (this.requestAnimationID) window.cancelAnimationFrame(this.requestAnimationID);
        const renderThData = this.renderThData = this.getRenderData(thParticles, thMaxHeight);
        for (let i = 0; i < renderThData.length; i++) {
            const cur = renderThData[i];
            cur.particle.draw(cur.height, {
                scrollLeft: instance.scrollLeft,
                scrollTop: instance.scrollTop
            });
        }
        const renderTdData = this.renderTdData = this.getRenderData(tdParticles, tdMaxHeight);
        for (let i = 0; i < renderTdData.length; i++) {
            const cur = renderTdData[i];
            cur.particle.draw(cur.height, {
                scrollLeft: instance.scrollLeft,
                scrollTop: instance.scrollTop
            });
        }
        // console.log(tdParticles, renderThData);
        // this.requestAnimationID = window.requestAnimationFrame(this.draw.bind(this));
    }

    getRenderData(data: any[], heights: any[]): any[] {
        const {
            instance: { scrollLeft, scrollTop, width: insWidth, height: insHeight }
        } = this;
        const res: any[] = [];
        for (let index = 0; index < data.length; index++) {
            const col: any = data[index];
            for (let j = 0; j < col.length; j++) {
                const particle = col[j];
                const {
                    position: { x, y },
                    style: { width }
                } = particle;
                const isInSide = this.boundContain(
                    {
                        x,
                        y,
                        width,
                        height: heights[index]
                    },
                    {
                        x: scrollLeft,
                        y: scrollTop,
                        width: insWidth,
                        height: insHeight
                    }
                );
                if (isInSide) {
                    res.push({
                        height: heights[index],
                        particle
                    });
                }
            }
        }
        // data.forEach((col: any, index: number) => {
        //     col.forEach((particle: any) => {
        //     });
        // });
        return res;
    }

    boundContain(targetRect: {
        x: number;
        y: number;
        width: number;
        height: number;
    }, sourceRect: {
        x: number;
        y: number;
        width: number;
        height: number;
    }) {
        // targetRect在sourceRect右边外
        const isRightOutside = targetRect.x > sourceRect.x + sourceRect.width;
        // targetRect在sourceRect左边外
        const isLeftOutside = targetRect.x + targetRect.width < sourceRect.x;
        // targetRect在sourceRect下边
        const isBottomOutside = targetRect.y > sourceRect.y + sourceRect.height;
        // targetRect在sourceRect上边
        const isTopOutside = targetRect.y + targetRect.height < sourceRect.y;
        const isOutSide = isRightOutside || isLeftOutside || isBottomOutside || isTopOutside;
        return !isOutSide;
    }

    boundPointContain(targetPoint: {
        x: number;
        y: number;
    }, sourceRect: {
        x: number;
        y: number;
        width: number;
        height: number;
    }) {
        return targetPoint.x >= sourceRect.x && targetPoint.y >= sourceRect.y && targetPoint.x <= sourceRect.x + sourceRect.width && targetPoint.y <= sourceRect.y + sourceRect.height;
    }

    updateScrollInfo() {
        const { allDataWidth, allDataHeight, instance } = this;
        this.instance.scrollWidth = Math.max(0, allDataWidth - instance.width + 1);
        this.instance.scrollHeight = Math.max(0, allDataHeight - instance.height + 1);
    }

    updateCurrentItem(value: any) {
        const { instance } = this;
        const {
            position: { x, y },
            style: { width }
        } = this.canEditorItem.particle;
        this.canEditorItem.particle.value = value;
        instance.ctx.clearRect(x, y, width, this.canEditorItem.height);
        this.canEditorItem.particle.draw(this.canEditorItem.height, {
            scrollLeft: instance.scrollLeft,
            scrollTop: instance.scrollTop
        });
    }

    scroll(x: number, y: number) {
        const {
            scrollWidth,
            scrollHeight
        } = this.instance;
        x = Math.max(0, x);
        x = Math.min(scrollWidth, x);
        y = Math.max(0, y);
        y = Math.min(scrollHeight, y);
        if (x === this.instance.scrollLeft && y === this.instance.scrollTop) {
            return;
        }
        this.instance.scrollLeft = x;
        this.instance.scrollTop = y;
        this.draw();
    }
}


